/**
 *  Hash function reference implementation
 *  David A. Wilson, 2008
 */

#include "dch32.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

BitSequence multtable[256][256];
int isInitialized = 0;

//state is where the initialized state gets returned
HashReturn Init(hashState *state, int hashbitlen){
  int i, j;

  //Make sure the input hash bit length is supported
  if((hashbitlen != 224) && (hashbitlen != 256) &&
     (hashbitlen != 384) && (hashbitlen != 512)){
    return BAD_HASHBITLEN;
  }

  if(isInitialized == 0){
    for(i=0;i<256;i++){
      for(j=0;j<256;j++){
	multtable[i][j] = (j==0) ? 0 : gf[(i+gfinv[j])%255];
      }
    }
    isInitialized = 1;
  } 

  state->hashbitlen = hashbitlen;
  state->numUnprocessed = 0;
  state->unprocessed = malloc(BLOCK_LENGTH_BYTES * sizeof(BitSequence));
  state->curr = malloc(BLOCK_LENGTH_BYTES * sizeof(BitSequence));
  memset(state->curr, 0, BLOCK_LENGTH_BYTES);

  state->datalen = 0;

  //initialize square-free sequence state
  state->p[0] = malloc(64*sizeof(unsigned char));
  state->p[1] = malloc(64*sizeof(unsigned char));
  state->p[2] = malloc(64*sizeof(unsigned char));
  for(i=0;i<63;i++){
    state->p[0][i]= 64-i;
  }
  state->p[1][0]=64;
  state->p[2][0]=64;
  state->top[0] = &(state->p[0][63]);
  state->top[1] = &(state->p[1][0]);
  state->top[2] = &(state->p[2][0]);
  state->parity = 1;
  state->small = 0;
  state->count = 0;
  state->move = 0x60;  //3 << 5
  return SUCCESS;
}

//process as much data as you can, update state
HashReturn Update(hashState *state, const BitSequence *data, 
		  DataLength databitlen){

  int max;

  if (databitlen == 0){
    return SUCCESS;
  }

  //If this is the beginning of the data block, we need to add sequence padding
  if(state->numUnprocessed == 0){
    setNextSequenceValue(state);
    state->numUnprocessed = 8;
  }

  max=8*BLOCK_LENGTH_BYTES - state->numUnprocessed;
  if (databitlen < max){
    max = databitlen;
  }
  databitlen -= max;
  state->datalen += max;

  memcpy(state->unprocessed + (state->numUnprocessed / 8), data, (max+7) / 8);
  state->numUnprocessed += max;
  data += max/8;
  
  while(state->numUnprocessed == PADDED_BLOCK_LENGTH_BITS){

    hashOneBlock(state);

    if(databitlen > 0){
      setNextSequenceValue(state);
      state->numUnprocessed = 8;
      max = PADDED_BLOCK_LENGTH_BITS - 8;
      if(databitlen < max){
	max = databitlen;
      }
      databitlen -= max;
      state->datalen += max;

      memcpy(state->unprocessed + (state->numUnprocessed / 8), data, (max+7)/8);
      state->numUnprocessed = max + 8;
      data += max/8;
    } else {
      state->numUnprocessed = 0;
    }

  }

  return SUCCESS;
}

//perform final output filtering etc., return result in hashval
HashReturn Final(hashState *state, BitSequence *hashval){
  int i;

  if(state->numUnprocessed == 0){
    state->numUnprocessed = 8;
    setNextSequenceValue(state);
  }

  //pad the last block with 1, 0s
  state->unprocessed[state->numUnprocessed / 8] |= 
    (0x80U >> (state->numUnprocessed % 8));
  state->unprocessed[state->numUnprocessed / 8] &= 
    (0xFF ^ ((1 << (7 - (state->numUnprocessed % 8))) - 1));
  memset(&state->unprocessed[state->numUnprocessed/8 + 1], 0, 
	 BLOCK_LENGTH_BYTES - state->numUnprocessed/8 - 1);

  state->numUnprocessed++;

  if(PADDED_BLOCK_LENGTH_BITS - state->numUnprocessed < 64){
    //we're near the end of this block, so the length will 
    //have to go to the next block.  Process this block first, 
    //then prepare the final block.

    hashOneBlock(state);
    memset(&state->unprocessed[0], 0, BLOCK_LENGTH_BYTES);
    setNextSequenceValue(state);
  }
  
  //copy length.  Be aware of endianness!
  for(i=0;i<sizeof(DataLength);i++){
    state->unprocessed[BLOCK_LENGTH_BYTES - 1 - i] = 
      ((BitSequence *)(&state->datalen))[i];
  }

  //set final sequence "move"
  state->unprocessed[0] &= 0x1F;  //00011111

  //hash the final (length) block
  hashOneBlock(state);

  memcpy(hashval, state->curr, state->hashbitlen / 8);

  free(state->unprocessed);
  free(state->curr);
  free(state->p[0]);
  free(state->p[1]);
  free(state->p[2]);

  return SUCCESS;
}

// All-at-once hash.
HashReturn Hash(int hashbitlen, const BitSequence *data, 
		DataLength databitlen, BitSequence *hashval){
  hashState *state = malloc(sizeof(hashState));
  HashReturn status = Init(state,  hashbitlen);
  if (status == SUCCESS){
    status = Update(state, data, databitlen);
    if (status == SUCCESS){
      status = Final(state, hashval);
    }
  }
  free(state);
  return status;
}

void hashOneBlock(hashState *state){
  int round;

  //add previous hash value with message plaintext
  // for Miyaguchi-Preneel iteration
  addInto(state->curr, state->unprocessed);

  for(round = 0; round < NUM_ROUNDS; round++){
    doSBox(state->unprocessed);
    doTransform(state->unprocessed);
    addInto(state->unprocessed, ROUND_KEY[round]);
  }

  //add processed value to previous value and plaintext for M-P iteration
  addInto(state->curr, state->unprocessed);

  //clear number of unprocessed
  state->numUnprocessed=0;
}

// Obtain the next element of a square-free sequence.  Set it as the first byte.
void setNextSequenceValue(hashState *state){
  //high-order three bits come from the Towers of Hanoi SF sequence
  //low-order five bits are a counter
  state->unprocessed[0] = state->move ^ state->count;
  state->count++;
  if(state->count == 32){
    state->count = 0;

    //counter has rolled over; generate next ToH sequence value
    if(state->parity){
      state->move = (state->small + 3*((state->small + 1) % 3)) << 5;
      state->top[state->small]--;
      state->small++;
      state->small %= 3;
      state->top[state->small]++;
      *(state->top[state->small]) = 1;
    }
    else{
      BitSequence a=1, b=2;
      if(state->small == 1){
	a=0;
      }
      else if(state->small == 2){
	b=0;
      }
      if(*(state->top[b]) > *(state->top[a])){
	//swap a, b
	b ^= a;
	a ^= b;
	b ^= a;
      }
      state->move = (b + 3*a) << 5;
      state->top[a]++;
      *(state->top[a]) = *(state->top[b]);
      state->top[b]--; 
    }
    state->parity ^= 1;
  }
}

void addInto(BitSequence *to, const BitSequence *from){
  int i;
  for(i=0; i<BLOCK_LENGTH_BYTES; i++){
    to[i] ^= from[i];
  }
}

void doSBox(BitSequence *data){
  int i;
  for(i=0; i<BLOCK_LENGTH_BYTES; i++){
    data[i] = SBOX[data[i]];
  }
}

void doTransform(BitSequence *data){
  int i, j, k;
  BitSequence *transformed, *t, x14, x13, x12, x11, x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0, y4, y3, y2, y1, y0, *multrow;
  transformed = malloc(BLOCK_LENGTH_BYTES*sizeof(BitSequence));


  for(k=0;k<60;k+=15){
    x14 = x13 = x12 = x11 = x10 = x9 = x8 = x7 = x6 = x5 = x4 = 0;
    x3 = data[63];
    x2 = data[62];
    x1 = data[61];
    x0 = data[60];
    t = &data[59];
    multrow = multtable[k];
    for(j=60; j>0; j -= 15){
      x14 = multrow[x14] ^ *t;t--;
      x13 = multrow[x13] ^ *t;t--;
      x12 = multrow[x12] ^ *t;t--;
      x11 = multrow[x11] ^ *t;t--;
      x10 = multrow[x10] ^ *t;t--;
      x9 = multrow[x9] ^ *t;t--;
      x8 = multrow[x8] ^ *t;t--;
      x7 = multrow[x7] ^ *t;t--;
      x6 = multrow[x6] ^ *t;t--;
      x5 = multrow[x5] ^ *t;t--;
      x4 = multrow[x4] ^ *t;t--;
      x3 = multrow[x3] ^ *t;t--;
      x2 = multrow[x2] ^ *t;t--;
      x1 = multrow[x1] ^ *t;t--;
      x0 = multrow[x0] ^ *t;t--;
    }

    for(j=k/3;j<255;j+=85){
      multrow = multtable[j];
      y4 = multrow[multrow[x14] ^ x9] ^ x4;
      y3 = multrow[multrow[x13] ^ x8] ^ x3;
      y2 = multrow[multrow[x12] ^ x7] ^ x2;
      y1 = multrow[multrow[x11] ^ x6] ^ x1;
      y0 = multrow[multrow[x10] ^ x5] ^ x0;

      for(i=j/5;i<255;i+=51){
	multrow = multtable[i];
      transformed[i % BLOCK_LENGTH_BYTES] = multrow[multrow[multrow[multrow[y4]
								    ^ y3]
							    ^ y2] 
						    ^ y1]
	                                     ^ y0;
      }
    }
  }
  x1 = x2 = 0;
  x0 = data[63];
  t = &data[62];
  multrow = multtable[48];
  while(t > data){
    x2 = multrow[x2] ^ *t; t--;
    x1 = multrow[x1] ^ *t; t--;
    x0 = multrow[x0] ^ *t; t--;
  }
  multrow = multtable[16];
  transformed[16] = multrow[multrow[x2] ^ x1] ^ x0;
  multrow = multtable[101];
  transformed[33] = multrow[multrow[x2] ^ x1] ^ x0;
  multrow = multtable[186];
  transformed[50] = multrow[multrow[x2] ^ x1] ^ x0;

  x0 = data[63];
  t=&data[62];
  multrow = multtable[63];
  while(t >= data){
    x0 = multrow[x0] ^ *t; t--;
  }
  transformed[63] = x0;

  memcpy(data, transformed, BLOCK_LENGTH_BYTES);
  free(transformed);
}

